/*-------------------------------------------------------------------------
 *
 * twophase.c
 *		两阶段提交支持函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *		src/backend/access/transam/twophase.c
 *
 * NOTES
 *		每个全局事务都与一个全局事务标识符 (GID) 关联。客户端使用 PREPARE TRANSACTION 命令
 *		为 postgres 事务分配一个 GID。
 *
 *		我们将所有活动的全局事务保存在一个共享内存数组中。
 *		当发出 PREPARE TRANSACTION 命令时，GID 在数组中为事务保留。
 *		这是在创建 WAL 条目之前完成的，因为保留会检查重复的 GID，
 *		如果已经存在一个处于准备状态的全局事务，则中止该事务。
 *
 *		一个全局事务 (gxact) 还拥有一个虚拟 PGPROC；这保持
 *		TransactionIdIsInProgress 考虑的 XID 处于运行状态。这也是一个
 *		将 gxact 的锁挂钩的便利 PGPROC。
 *
 *		为防止崩溃而恢复准备的事务的信息现在存储在 WAL 中，适用于一般情况。
 *		在某些情况下，准备 GXACT 和提交/中止之间会有一个延长的时间段，
 *		在这种情况下，我们需要在永久存储中单独记录准备的交易数据。
 *		这包括锁定信息、待处理通知等。所有这些状态信息会被写入
 *		pg_twophase 目录中的每个事务状态文件。
 *		所有准备的事务将在关机之前写入。
 *
 *		状态数据的生命周期如下：
 *
 *		* 在 PREPARE TRANSACTION 后端仅将状态数据写入 WAL，并且
 *		  将 WAL 记录的起始指针存储在 gxact->prepare_start_lsn 中。
 *		* 如果提交在检查点之前发生，则后端将使用 prepare_start_lsn 从 WAL 中读取数据。
 *		* 在检查点时，状态数据复制到 pg_twophase 目录中的文件中并 fsynced
 *		* 如果提交发生在检查点之后，则后端从文件中读取状态数据。
 *
 *		在重放和复制期间，TwoPhaseState 还保存有关尚未移动到磁盘的
 *		活动准备事务的信息。
 *
 *		两阶段记录的重放遵循以下规则：
 *
 *		* 在恢复开始时，pg_twophase 被扫描一次，填充
 *		  TwoPhaseState，标记为 gxact->inredo 和
 *		  gxact->ondisk。时间跨度早于重做位置的 XID 地平线的两阶段文件数据被丢弃。
 *		* 在 PREPARE 重做时，事务被添加到 TwoPhaseState->prepXacts。
 *		  gxact->inredo 为这些条目设置为 true。
 *		* 在检查点时，我们遍历设置为 gxact->inredo 的条目
 *		  的 TwoPhaseState->prepXacts，它们在重做地平线之后。
 *		  我们将其保存到磁盘，然后将 gxact->ondisk 切换为 true。
 *		* 在提交/中止时，我们将条目从 TwoPhaseState->prepXacts 中删除。
 *		  如果 gxact->ondisk 为 true，则相应的条目也从磁盘中删除。
 *		* RecoverPreparedTransactions()、StandbyRecoverPreparedTransactions()
 *		  和 PrescanPreparedTransactions() 已被修改为遍历尚未移至磁盘的
 *		  gxact->inredo 条目。
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include "access/commit_ts.h"
#include "access/htup_details.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/twophase.h"
#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "access/xlogreader.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "replication/origin.h"
#include "replication/syncrep.h"
#include "replication/walsender.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/md.h"
#include "storage/predicate.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"

/*
 * 目录，Two-phase commit 文件在 PGDATA 中的位置
 */
#define TWOPHASE_DIR "pg_twophase"

/* GUC 变量，启动后无法更改 */
int			max_prepared_xacts = 0;

/*
 * 该结构描述一个处于准备状态的全局事务
 * 或者尝试变为准备状态。
 *
 * 全局事务的生命周期为：
 *
 * 1. 在检查请求的 GID 未被使用后，在 TwoPhaseState->prepXacts 数组中
 * 设置一个具有正确 GID 和 valid = false 的条目，并将其标记为被我的后端锁定。
 *
 * 2. 成功完成准备后，将 valid = true，并将引用的 PGPROC 进入全局 ProcArray。
 *
 * 3. 要开始 COMMIT PREPARED 或 ROLLBACK PREPARED，检查条目是否
 * 有效且未被锁定，然后通过将我当前的后端 ID 存储到 locking_backend
 * 来将条目标记为锁定。这防止了对同一准备事务的并发提交或回滚尝试。
 *
 * 4. 在完成 COMMIT PREPARED 或 ROLLBACK PREPARED 后，从 ProcArray
 * 和 TwoPhaseState->prepXacts 数组中移除该条目，并将其返回到 freelist。
 *
 * 请注意，如果准备事务在步骤 1 和 2 之间失败，则必须移除该条目，以便
 * GID 和 GlobalTransaction 结构可以被重用。参见 AtAbort_Twophase()。
 *
 * typedef struct GlobalTransactionData *GlobalTransaction 出现在
 * twophase.h
 */

typedef struct GlobalTransactionData
{
	GlobalTransaction next;		/* 空闲链表的列表链接 */
	int			pgprocno;		/* 关联的虚拟 PGPROC 的 ID */
	BackendId	dummyBackendId; /* 对后端的类似后端 ID */
	TimestampTz prepared_at;	/* 准备时间 */

	/*
	 * 请注意，我们需要跟踪每个 GXACT 的两个 LSN。我们跟踪
	 * 开始 LSN，因为这是我们在提交准备好的 GXACT 时必须使用的
	 * 从 WAL 读取状态数据的地址。我们跟踪结束 LSN，因为这是我们
	 * 在提交之前需要等待的 LSN。
	 */
	XLogRecPtr	prepare_start_lsn;	/* 准备记录开始的 XLOG 偏移量 */
	XLogRecPtr	prepare_end_lsn;	/* 准备记录结束的 XLOG 偏移量 */
	TransactionId xid;			/* GXACT id */

	Oid			owner;			/* 执行该事务的用户 ID */
	BackendId	locking_backend;	/* 当前处理该事务的后端 */
	bool		valid;			/* 如果 PGPROC 条目在 proc 数组中则为 true */
	bool		ondisk;			/* 如果准备状态文件在磁盘上则为 true */
	bool		inredo;			/* 如果条目是通过 xlog_redo 添加的则为 true */
	char		gid[GIDSIZE];	/* 分配给准备事务的 GID */
}			GlobalTransactionData;

/*
 * 两阶段提交共享状态。对该结构的访问受到
 * TwoPhaseStateLock 的保护。
 */
typedef struct TwoPhaseStateData
{
	/* 自由 GlobalTransactionData 结构的链表头 */
	GlobalTransaction freeGXacts;

	/* 有效 prepXacts 条目的数量。 */
	int			numPrepXacts;

	/* 该数组中有 max_prepared_xacts 项 */
	GlobalTransaction prepXacts[FLEXIBLE_ARRAY_MEMBER];
} TwoPhaseStateData;

static TwoPhaseStateData *TwoPhaseState;

/*
 * 当前被我们锁定的全局事务条目（如果有）。请注意，任何
 * 访问该变量指向的条目必须受到 TwoPhaseStateLock 的保护，
 * 虽然显然指针本身不需要（因为它只是本地内存）。
 */
static GlobalTransaction MyLockedGxact = NULL;

static bool twophaseExitRegistered = false;

static void fc_RecordTransactionCommitPrepared(TransactionId fc_xid,
											int fc_nchildren,
											TransactionId *fc_children,
											int fc_nrels,
											RelFileNode *fc_rels,
											int fc_nstats,
											xl_xact_stats_item *fc_stats,
											int fc_ninvalmsgs,
											SharedInvalidationMessage *fc_invalmsgs,
											bool fc_initfileinval,
											const char *fc_gid);
static void fc_RecordTransactionAbortPrepared(TransactionId fc_xid,
										   int fc_nchildren,
										   TransactionId *fc_children,
										   int fc_nrels,
										   RelFileNode *fc_rels,
										   int fc_nstats,
										   xl_xact_stats_item *fc_stats,
										   const char *fc_gid);
static void fc_ProcessRecords(char *fc_bufptr, TransactionId fc_xid,
						   const TwoPhaseCallback fc_callbacks[]);
static void fc_RemoveGXact(GlobalTransaction fc_gxact);

static void fc_XlogReadTwoPhaseData(XLogRecPtr fc_lsn, char **fc_buf, int *fc_len);
static char *fc_ProcessTwoPhaseBuffer(TransactionId fc_xid,
								   XLogRecPtr fc_prepare_start_lsn,
								   bool fc_fromdisk, bool fc_setParent, bool fc_setNextXid);
static void fc_MarkAsPreparingGuts(GlobalTransaction fc_gxact, TransactionId fc_xid,
								const char *fc_gid, TimestampTz fc_prepared_at, Oid fc_owner,
								Oid fc_databaseid);
static void fc_RemoveTwoPhaseFile(TransactionId fc_xid, bool fc_giveWarning);
static void fc_RecreateTwoPhaseFile(TransactionId fc_xid, void *fc_content, int fc_len);

/*
 * 共享内存的初始化
 */
Size TwoPhaseShmemSize(void)
{
	Size		fc_size;

	/* 需要固定结构、指针数组和 GTD 结构 */
	fc_size = offsetof(TwoPhaseStateData, prepXacts);
	fc_size = add_size(fc_size, mul_size(max_prepared_xacts,
								   sizeof(GlobalTransaction)));
	fc_size = MAXALIGN(fc_size);
	fc_size = add_size(fc_size, mul_size(max_prepared_xacts,
								   sizeof(GlobalTransactionData)));

	return fc_size;
}

void TwoPhaseShmemInit(void)
{
	bool		fc_found;

	TwoPhaseState = ShmemInitStruct("Prepared Transaction Table",
									TwoPhaseShmemSize(),
									&fc_found);
	if (!IsUnderPostmaster)
	{
		GlobalTransaction fc_gxacts;
		int			fc_i;

		Assert(!fc_found);
		TwoPhaseState->freeGXacts = NULL;
		TwoPhaseState->numPrepXacts = 0;

		/*
		 * 初始化自由 GlobalTransactionData 结构的链表
		 */
		fc_gxacts = (GlobalTransaction)
			((char *) TwoPhaseState +
			 MAXALIGN(offsetof(TwoPhaseStateData, prepXacts) +
					  sizeof(GlobalTransaction) * max_prepared_xacts));
		for (fc_i = 0; fc_i < max_prepared_xacts; fc_i++)
		{
			/* 插入到链表中 */
			fc_gxacts[fc_i].next = TwoPhaseState->freeGXacts;
			TwoPhaseState->freeGXacts = &fc_gxacts[fc_i];

			/* 将其与由 InitProcGlobal 分配的 PGPROC 关联 */
			fc_gxacts[fc_i].pgprocno = PreparedXactProcs[fc_i].pgprocno;

			/*
			 * 为每个虚拟进程分配一个唯一 ID，以便虚拟后端 ID 的范围
			 * 紧接在正常后端 ID 范围之后。我们不敢为虚拟进程分配一个真正的
			 * 后端 ID，因为准备好的事务不会像真实的后端 ID 那样参与缓存
			 * 失效，但为它们拥有一个唯一的 ID 仍然是方便的。此安排
			 * 允许你分配一个大小为 (MaxBackends +
			 * max_prepared_xacts + 1) 的数组，并为每个后端和
			 * 准备好的事务保留一个槽位。目前 multixact.c 使用了这种
			 * 技术。
			 */
			fc_gxacts[fc_i].dummyBackendId = MaxBackends + 1 + fc_i;
		}
	}
	else
		Assert(fc_found);
}

/*
 * 退出钩子以解锁我们正在处理的全局事务条目。
 */
static void fc_AtProcExit_Twophase(int fc_code, Datum fc_arg)
{
	/* 逻辑与中止相同 */
	AtAbort_Twophase();
}

/*
 * 中止钩子以解锁我们正在处理的全局事务条目。
 */
void AtAbort_Twophase(void)
{
	if (MyLockedGxact == NULL)
		return;

	/*
	 * 对于被锁定的全局事务条目我们该怎么办？如果我们正在准备事务，
	 * 但尚未写入 WAL 记录和状态文件，则该事务不得视为已准备。 同样，
	 * 如果我们正在完成一个已经准备好的事务，并在已经将第二阶段
	 * 提交或回滚记录写入 WAL 后失败，则该事务不再应视为已准备。
	 * 在这些情况下，只需从共享内存中删除条目。
	 *
	 * 否则，条目必须保留在原位，以便稍后完成该事务，因此只需解锁它。
	 *
	 * 如果在准备阶段中止，且在写入 WAL 记录后，我们可能尚未将
	 * 所有锁和其他状态转移到准备的事务中。同样，如果在提交或回滚期间
	 * 中止，且在写入 WAL 记录后，我们可能尚未释放事务所持有的所有
	 * 资源。在这些情况下，内存中的状态可能是错误的，但为时已晚，
	 * 无法撤销。
	 */
	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	if (!MyLockedGxact->valid)
		fc_RemoveGXact(MyLockedGxact);
	else
		MyLockedGxact->locking_backend = InvalidBackendId;
	LWLockRelease(TwoPhaseStateLock);

	MyLockedGxact = NULL;
}

/*
 * 这在我们完成状态转移到准备好的 PGPROC 条目后被调用。
 */
void PostPrepare_Twophase(void)
{
	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	MyLockedGxact->locking_backend = InvalidBackendId;
	LWLockRelease(TwoPhaseStateLock);

	MyLockedGxact = NULL;
}


/*
 * MarkAsPreparing
 *		保留给定事务的 GID。
 */
GlobalTransaction
MarkAsPreparing(TransactionId fc_xid, const char *fc_gid,
				TimestampTz fc_prepared_at, Oid fc_owner, Oid fc_databaseid)
{
	GlobalTransaction fc_gxact;
	int			fc_i;

	if (strlen(fc_gid) >= GIDSIZE)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("transaction identifier \"%s\" is too long",
						fc_gid)));

	/* 如果功能已禁用，立即失败 */
	if (max_prepared_xacts == 0)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("prepared transactions are disabled"),
				 errhint("Set max_prepared_transactions to a nonzero value.")));

	/* 在第一次调用时，注册退出钩子 */
	if (!twophaseExitRegistered)
	{
		before_shmem_exit(fc_AtProcExit_Twophase, 0);
		twophaseExitRegistered = true;
	}

	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);

	/* 检查冲突的 GID */
	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		fc_gxact = TwoPhaseState->prepXacts[fc_i];
		if (strcmp(fc_gxact->gid, fc_gid) == 0)
		{
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("transaction identifier \"%s\" is already in use",
							fc_gid)));
		}
	}

	/* 从空闲列表中获取一个免费 gxact */
	if (TwoPhaseState->freeGXacts == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("maximum number of prepared transactions reached"),
				 errhint("Increase max_prepared_transactions (currently %d).",
						 max_prepared_xacts)));
	fc_gxact = TwoPhaseState->freeGXacts;
	TwoPhaseState->freeGXacts = fc_gxact->next;

	fc_MarkAsPreparingGuts(fc_gxact, fc_xid, fc_gid, fc_prepared_at, fc_owner, fc_databaseid);

	fc_gxact->ondisk = false;

	/* 并将其插入到活动数组中 */
	Assert(TwoPhaseState->numPrepXacts < max_prepared_xacts);
	TwoPhaseState->prepXacts[TwoPhaseState->numPrepXacts++] = fc_gxact;

	LWLockRelease(TwoPhaseStateLock);

	return fc_gxact;
}

/*
 * MarkAsPreparingGuts
 *
 * 这使用 gxact 结构并将其放入活动数组中。
 * 注意：这在崩溃后重新加载 gxact 时也会使用；因此避免
 * 假设我们可以使用太多后端上下文。
 *
 * 注意：此函数应该在持有适当锁的情况下调用。
 */
static void fc_MarkAsPreparingGuts(GlobalTransaction fc_gxact, TransactionId fc_xid, const char *fc_gid,
					TimestampTz fc_prepared_at, Oid fc_owner, Oid fc_databaseid)
{
	PGPROC	   *fc_proc;
	int			fc_i;

	Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));

	Assert(fc_gxact != NULL);
	fc_proc = &ProcGlobal->allProcs[fc_gxact->pgprocno];

	/* 初始化 PGPROC 条目 */
	MemSet(fc_proc, 0, sizeof(PGPROC));
	fc_proc->pgprocno = fc_gxact->pgprocno;
	SHMQueueElemInit(&(fc_proc->links));
	fc_proc->waitStatus = PROC_WAIT_STATUS_OK;
	if (LocalTransactionIdIsValid(MyProc->lxid))
	{
		/* 克隆 VXID，以便 TwoPhaseGetXidByVirtualXID() 找到 */
		fc_proc->lxid = MyProc->lxid;
		fc_proc->backendId = MyBackendId;
	}
	else
	{
		Assert(AmStartupProcess() || !IsPostmasterEnvironment);
		/* GetLockConflicts() 使用此方法指定对 XID 的等待 */
		fc_proc->lxid = fc_xid;
		fc_proc->backendId = InvalidBackendId;
	}
	fc_proc->xid = fc_xid;
	Assert(fc_proc->xmin == InvalidTransactionId);
	fc_proc->delayChkptFlags = 0;
	fc_proc->statusFlags = 0;
	fc_proc->pid = 0;
	fc_proc->databaseId = fc_databaseid;
	fc_proc->roleId = fc_owner;
	fc_proc->tempNamespaceId = InvalidOid;
	fc_proc->isBackgroundWorker = true;
	fc_proc->lwWaiting = LW_WS_NOT_WAITING;
	fc_proc->lwWaitMode = 0;
	fc_proc->waitLock = NULL;
	fc_proc->waitProcLock = NULL;
	pg_atomic_init_u64(&fc_proc->waitStart, 0);
	for (fc_i = 0; fc_i < NUM_LOCK_PARTITIONS; fc_i++)
		SHMQueueInit(&(fc_proc->myProcLocks[fc_i]));
	/* subxid 数据必须由 GXactLoadSubxactData 后续填充 */
	fc_proc->subxidStatus.overflowed = false;
	fc_proc->subxidStatus.count = 0;

	fc_gxact->prepared_at = fc_prepared_at;
	fc_gxact->xid = fc_xid;
	fc_gxact->owner = fc_owner;
	fc_gxact->locking_backend = MyBackendId;
	fc_gxact->valid = false;
	fc_gxact->inredo = false;
	strcpy(fc_gxact->gid, fc_gid);

	/*
	 * 记住我们已经锁定了这个 GlobalTransaction 条目。如果在此之后我们中止，必须释放它。
	 */
	MyLockedGxact = fc_gxact;
}

/*
 * GXactLoadSubxactData
 *
 * 如果正在持久化的事务有任何子事务，这必须在 MarkAsPrepared() 之前调用，以便将信息加载到虚拟 PGPROC 中。
 */
static void fc_GXactLoadSubxactData(GlobalTransaction fc_gxact, int fc_nsubxacts,
					 TransactionId *fc_children)
{
	PGPROC	   *fc_proc = &ProcGlobal->allProcs[fc_gxact->pgprocno];

	/* 我们不需要额外的锁，因为 GXACT 还不是有效的 */
	if (fc_nsubxacts > PGPROC_MAX_CACHED_SUBXIDS)
	{
		fc_proc->subxidStatus.overflowed = true;
		fc_nsubxacts = PGPROC_MAX_CACHED_SUBXIDS;
	}
	if (fc_nsubxacts > 0)
	{
		memcpy(fc_proc->subxids.xids, fc_children,
			   fc_nsubxacts * sizeof(TransactionId));
		fc_proc->subxidStatus.count = fc_nsubxacts;
	}
}

/*
 * MarkAsPrepared
 *		将 GXACT 标记为完全有效，并将其加入全局 ProcArray。
 *
 * lock_held 指示调用者是否已经持有 TwoPhaseStateLock。
 */
static void fc_MarkAsPrepared(GlobalTransaction fc_gxact, bool fc_lock_held)
{
	/* 此处锁定可能过于严格，但我对此不太确定... */
	if (!fc_lock_held)
		LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	Assert(!fc_gxact->valid);
	fc_gxact->valid = true;
	if (!fc_lock_held)
		LWLockRelease(TwoPhaseStateLock);

	/*
	 * 将其放入全局 ProcArray，以便 TransactionIdIsInProgress 将
	 * XID 视为仍在运行。
	 */
	ProcArrayAdd(&ProcGlobal->allProcs[fc_gxact->pgprocno]);
}

/*
 * LockGXact
 *		找到已准备的事务并将其标记为正在进行 COMMIT 或 PREPARE。
 */
static GlobalTransaction
fc_LockGXact(const char *fc_gid, Oid fc_user)
{
	int			fc_i;

	/* 在第一次调用时，注册退出钩子 */
	if (!twophaseExitRegistered)
	{
		before_shmem_exit(fc_AtProcExit_Twophase, 0);
		twophaseExitRegistered = true;
	}

	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);

	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];
		PGPROC	   *fc_proc = &ProcGlobal->allProcs[fc_gxact->pgprocno];

		/* 忽略尚未有效的 GIDs */
		if (!fc_gxact->valid)
			continue;
		if (strcmp(fc_gxact->gid, fc_gid) != 0)
			continue;

		/* 找到了，但是否还有其他人锁定了它？ */
		if (fc_gxact->locking_backend != InvalidBackendId)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("prepared transaction with identifier \"%s\" is busy",
							fc_gid)));

		if (fc_user != fc_gxact->owner && !superuser_arg(fc_user))
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("permission denied to finish prepared transaction"),
					 errhint("Must be superuser or the user that prepared the transaction.")));

		/*
		 * 注意：可能允许从另一个数据库进行提交；但目前已知 NOTIFY 无法工作，
		 * 可能还有其他一些问题。因此在有人有动力解决此问题之前不允许。
		 */
		if (MyDatabaseId != fc_proc->databaseId)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("prepared transaction belongs to another database"),
					 errhint("Connect to the database where the transaction was prepared to finish it.")));

		/* 我锁定它是可以的 */
		fc_gxact->locking_backend = MyBackendId;
		MyLockedGxact = fc_gxact;

		LWLockRelease(TwoPhaseStateLock);

		return fc_gxact;
	}

	LWLockRelease(TwoPhaseStateLock);

	ereport(ERROR,
			(errcode(ERRCODE_UNDEFINED_OBJECT),
			 errmsg("prepared transaction with identifier \"%s\" does not exist",
					fc_gid)));

	/* 不可达 */
	return NULL;
}

/*
 * RemoveGXact
 *		从共享内存数组中移除已准备的事务。
 *
 * 注意：调用者应该已经将其从 ProcArray 中移除
 */
static void fc_RemoveGXact(GlobalTransaction fc_gxact)
{
	int			fc_i;

	Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));

	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		if (fc_gxact == TwoPhaseState->prepXacts[fc_i])
		{
			/* 从活动数组中移除 */
			TwoPhaseState->numPrepXacts--;
			TwoPhaseState->prepXacts[fc_i] = TwoPhaseState->prepXacts[TwoPhaseState->numPrepXacts];

			/* 并将其放回自由列表 */
			fc_gxact->next = TwoPhaseState->freeGXacts;
			TwoPhaseState->freeGXacts = fc_gxact;

			return;
		}
	}

	elog(ERROR, "failed to find %p in GlobalTransaction array", fc_gxact);
}

/*
 * 返回所有已准备事务的数组以供用户级
 * 函数 pg_prepared_xact 使用。
 *
 * 返回的数组及其所有元素都是内部数据
 * 结构的副本，以最小化我们需要持有 TwoPhaseStateLock 的时间。
 *
 * 警告 -- 我们甚至返回那些尚未完全准备好的事务。
 * 调用者应过滤掉这些事务，如果他不想要它们。
 *
 * 返回的数组是 palloc'd。
 */
static int fc_GetPreparedTransactionList(GlobalTransaction *fc_gxacts)
{
	GlobalTransaction fc_array;
	int			fc_num;
	int			fc_i;

	LWLockAcquire(TwoPhaseStateLock, LW_SHARED);

	if (TwoPhaseState->numPrepXacts == 0)
	{
		LWLockRelease(TwoPhaseStateLock);

		*fc_gxacts = NULL;
		return 0;
	}

	fc_num = TwoPhaseState->numPrepXacts;
	fc_array = (GlobalTransaction) palloc(sizeof(GlobalTransactionData) * fc_num);
	*fc_gxacts = fc_array;
	for (fc_i = 0; fc_i < fc_num; fc_i++)
		memcpy(fc_array + fc_i, TwoPhaseState->prepXacts[fc_i],
			   sizeof(GlobalTransactionData));

	LWLockRelease(TwoPhaseStateLock);

	return fc_num;
}


/* pg_prepared_xact 的工作状态 */
typedef struct
{
	GlobalTransaction array;
	int			ngxacts;
	int			currIdx;
} Working_State;

/*
 * pg_prepared_xact
 *		生成每个已准备事务的单行视图。
 *
 * 该函数在这里，以便我们不必导出
 * GlobalTransactionData 结构定义。
 */
Datum pg_prepared_xact(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	Working_State *fc_status;

	if (SRF_IS_FIRSTCALL())
	{
		TupleDesc	fc_tupdesc;
		MemoryContext fc_oldcontext;

		/* 为跨调用持久性创建一个函数上下文 */
		fc_funcctx = SRF_FIRSTCALL_INIT();

		/*
		 * 切换到适合多个函数调用的内存上下文
		 */
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		/* 为结果元组构建 tupdesc */
		/* 这最好与 system_views.sql 中的 pg_prepared_xacts 视图匹配 */
		fc_tupdesc = CreateTemplateTupleDesc(5);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "transaction",
						   XIDOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "gid",
						   TEXTOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "prepared",
						   TIMESTAMPTZOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 4, "ownerid",
						   OIDOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 5, "dbid",
						   OIDOID, -1, 0);

		fc_funcctx->tuple_desc = BlessTupleDesc(fc_tupdesc);

		/*
		 * 收集所有 2PC 状态信息，我们将格式化并发送
		 * 作为结果集。
		 */
		fc_status = (Working_State *) palloc(sizeof(Working_State));
		fc_funcctx->user_fctx = (void *) fc_status;

		fc_status->ngxacts = fc_GetPreparedTransactionList(&fc_status->array);
		fc_status->currIdx = 0;

		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_status = (Working_State *) fc_funcctx->user_fctx;

	while (fc_status->array != NULL && fc_status->currIdx < fc_status->ngxacts)
	{
		GlobalTransaction fc_gxact = &fc_status->array[fc_status->currIdx++];
		PGPROC	   *fc_proc = &ProcGlobal->allProcs[fc_gxact->pgprocno];
		Datum		fc_values[5];
		bool		fc_nulls[5];
		HeapTuple	fc_tuple;
		Datum		fc_result;

		if (!fc_gxact->valid)
			continue;

		/*
		 * 使用适当的数据形成元组。
		 */
		MemSet(fc_values, 0, sizeof(fc_values));
		MemSet(fc_nulls, 0, sizeof(fc_nulls));

		fc_values[0] = TransactionIdGetDatum(fc_proc->xid);
		fc_values[1] = CStringGetTextDatum(fc_gxact->gid);
		fc_values[2] = TimestampTzGetDatum(fc_gxact->prepared_at);
		fc_values[3] = ObjectIdGetDatum(fc_gxact->owner);
		fc_values[4] = ObjectIdGetDatum(fc_proc->databaseId);

		fc_tuple = heap_form_tuple(fc_funcctx->tuple_desc, fc_values, fc_nulls);
		fc_result = HeapTupleGetDatum(fc_tuple);
		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	}

	SRF_RETURN_DONE(fc_funcctx);
}

/*
 * TwoPhaseGetGXact
 *		获取指定 XID 的已准备事务的 GlobalTransaction 结构。
 *
 * 如果 lock_held 被设置为 true，则不会获取 TwoPhaseStateLock，因此
 * 调用者最好持有它。
 */
static GlobalTransaction
fc_TwoPhaseGetGXact(TransactionId fc_xid, bool fc_lock_held)
{
	GlobalTransaction fc_result = NULL;
	int			fc_i;

	static TransactionId fc_cached_xid = InvalidTransactionId;
	static GlobalTransaction fc_cached_gxact = NULL;

	Assert(!fc_lock_held || LWLockHeldByMe(TwoPhaseStateLock));

	/*
	 * 在恢复期间，将会反复调用 COMMIT PREPARED 或 ABORT PREPARED，对于同一个 XID。我们可以通过简单的缓存来节省工作。
	 */
	if (fc_xid == fc_cached_xid)
		return fc_cached_gxact;

	if (!fc_lock_held)
		LWLockAcquire(TwoPhaseStateLock, LW_SHARED);

	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];

		if (fc_gxact->xid == fc_xid)
		{
			fc_result = fc_gxact;
			break;
		}
	}

	if (!fc_lock_held)
		LWLockRelease(TwoPhaseStateLock);

	if (fc_result == NULL)			/* 不应该发生 */
		elog(ERROR, "failed to find GlobalTransaction for xid %u", fc_xid);

	fc_cached_xid = fc_xid;
	fc_cached_gxact = fc_result;

	return fc_result;
}

/*
 * TwoPhaseGetXidByVirtualXID
 *		查找自上次启动以来准备的事务中的 VXID。
 *
 * (这将不会找到已恢复的事务。) 如果匹配项超过一个，则返回任意一个，并将 "have_more" 设置为 true。要见证多个匹配，单个 BackendId 必须消耗 2^32 的 LXIDs，中间没有数据库重启。
 */
TransactionId TwoPhaseGetXidByVirtualXID(VirtualTransactionId fc_vxid,
						   bool *fc_have_more)
{
	int			fc_i;
	TransactionId fc_result = InvalidTransactionId;

	Assert(VirtualTransactionIdIsValid(fc_vxid));
	LWLockAcquire(TwoPhaseStateLock, LW_SHARED);

	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];
		PGPROC	   *fc_proc;
		VirtualTransactionId fc_proc_vxid;

		if (!fc_gxact->valid)
			continue;
		fc_proc = &ProcGlobal->allProcs[fc_gxact->pgprocno];
		GET_VXID_FROM_PGPROC(fc_proc_vxid, *fc_proc);
		if (VirtualTransactionIdEquals(fc_vxid, fc_proc_vxid))
		{
			/* 启动进程将 proc->backendId 设置为 InvalidBackendId。 */
			Assert(!fc_gxact->inredo);

			if (fc_result != InvalidTransactionId)
			{
				*fc_have_more = true;
				break;
			}
			fc_result = fc_gxact->xid;
		}
	}

	LWLockRelease(TwoPhaseStateLock);

	return fc_result;
}

/*
 * TwoPhaseGetDummyBackendId
 *		获取由 XID 指定的准备事务的虚拟后端 ID
 *
 * 虚拟后端 ID 类似于真实后端的真实后端 ID。
 * 它们从 MaxBackends + 1 开始，并且在所有当前活跃的真实后端和准备事务中是唯一的。如果 lock_held 被设置为 true，
 * 则不会获得 TwoPhaseStateLock，因此调用者最好持有它。
 */
BackendId
TwoPhaseGetDummyBackendId(TransactionId fc_xid, bool fc_lock_held)
{
	GlobalTransaction fc_gxact = fc_TwoPhaseGetGXact(fc_xid, fc_lock_held);

	return fc_gxact->dummyBackendId;
}

/*
 * TwoPhaseGetDummyProc
 *		获取代表由 XID 指定的准备事务的 PGPROC
 *
 * 如果 lock_held 被设置为 true，则不会获得 TwoPhaseStateLock，因此调用者最好持有它。
 */
PGPROC * TwoPhaseGetDummyProc(TransactionId fc_xid, bool fc_lock_held)
{
	GlobalTransaction fc_gxact = fc_TwoPhaseGetGXact(fc_xid, fc_lock_held);

	return &ProcGlobal->allProcs[fc_gxact->pgprocno];
}

/************************************************************************/
/* 状态文件支持													*/
/************************************************************************/

#define TwoPhaseFilePath(path, xid) \
	snprintf(path, MAXPGPATH, TWOPHASE_DIR "/%08X", xid)

/*
 * 2PC 状态文件格式：
 *
 *	1. TwoPhaseFileHeader
 *	2. TransactionId[] (子事务)
 *	3. RelFileNode[] (在提交时要删除的文件)
 *	4. RelFileNode[] (在中止时要删除的文件)
 *	5. SharedInvalidationMessage[] (在提交时发送的失效消息)
 *	6. TwoPhaseRecordOnDisk
 *	7. ...
 *	8. TwoPhaseRecordOnDisk (结束哨兵，rmid == TWOPHASE_RM_END_ID)
 *	9. 校验和 (CRC-32C)
 *
 * 除了最后的校验和之外，每个段都进行了 MAXALIGN 处理。
 */

/*
 * 2PC 状态文件的头部
 */
#define TWOPHASE_MAGIC	0x57F94534	/* 格式标识符 */

typedef xl_xact_prepare TwoPhaseFileHeader;

/*
 * 状态文件中每个记录的头部
 *
 * 注意：len 仅计算 rmgr 数据，而不计算 TwoPhaseRecordOnDisk 头部。
 * rmgr 数据将从 MAXALIGN 边界开始存储。
 */
typedef struct TwoPhaseRecordOnDisk
{
	uint32		len;			/* rmgr 数据的长度 */
	TwoPhaseRmgrId rmid;		/* 此记录的资源管理器 */
	uint16		info;			/* rmgr 使用的标志位 */
} TwoPhaseRecordOnDisk;

/*
 * 在准备期间，状态文件在写入 WAL 和实际状态文件之前在内存中组装。我们为此使用一系列 StateFileChunk 块。
 */
typedef struct StateFileChunk
{
	char	   *data;
	uint32		len;
	struct StateFileChunk *next;
} StateFileChunk;

static struct xllist
{
	StateFileChunk *head;		/* 链中的第一个数据块 */
	StateFileChunk *tail;		/* 链中的最后一个块 */
	uint32		num_chunks;
	uint32		bytes_free;		/* 尾块中剩余的空闲字节 */
	uint32		total_len;		/* 链中的总数据字节 */
}			records;


/*
 * 将一块数据附加到记录数据结构中。
 *
 * 注意：每个块都填充到 MAXALIGN 的倍数。这在稍后读取文件时必须考虑！
 *
 * 数据会被复制，因此调用者可以在之后自由修改它。
 */
static void fc_save_state_data(const void *fc_data, uint32 fc_len)
{
	uint32		fc_padlen = MAXALIGN(fc_len);

	if (fc_padlen > records.bytes_free)
	{
		records.tail->next = palloc0(sizeof(StateFileChunk));
		records.tail = records.tail->next;
		records.tail->len = 0;
		records.tail->next = NULL;
		records.num_chunks++;

		records.bytes_free = Max(fc_padlen, 512);
		records.tail->data = palloc(records.bytes_free);
	}

	memcpy(((char *) records.tail->data) + records.tail->len, fc_data, fc_len);
	records.tail->len += fc_padlen;
	records.bytes_free -= fc_padlen;
	records.total_len += fc_padlen;
}

/*
 * 开始准备一个状态文件。
 *
 * 初始化数据结构并插入 2PC 文件头记录。
 */
void StartPrepare(GlobalTransaction fc_gxact)
{
	PGPROC	   *fc_proc = &ProcGlobal->allProcs[fc_gxact->pgprocno];
	TransactionId fc_xid = fc_gxact->xid;
	TwoPhaseFileHeader fc_hdr;
	TransactionId *fc_children;
	RelFileNode *fc_commitrels;
	RelFileNode *fc_abortrels;
	xl_xact_stats_item *fc_abortstats = NULL;
	xl_xact_stats_item *fc_commitstats = NULL;
	SharedInvalidationMessage *fc_invalmsgs;

	/* 初始化链表 */
	records.head = palloc0(sizeof(StateFileChunk));
	records.head->len = 0;
	records.head->next = NULL;

	records.bytes_free = Max(sizeof(TwoPhaseFileHeader), 512);
	records.head->data = palloc(records.bytes_free);

	records.tail = records.head;
	records.num_chunks = 1;

	records.total_len = 0;

	/* 创建头部 */
	fc_hdr.magic = TWOPHASE_MAGIC;
	fc_hdr.total_len = 0;			/* EndPrepare 将填充此内容 */
	fc_hdr.xid = fc_xid;
	fc_hdr.database = fc_proc->databaseId;
	fc_hdr.prepared_at = fc_gxact->prepared_at;
	fc_hdr.owner = fc_gxact->owner;
	fc_hdr.nsubxacts = xactGetCommittedChildren(&fc_children);
	fc_hdr.ncommitrels = smgrGetPendingDeletes(true, &fc_commitrels);
	fc_hdr.nabortrels = smgrGetPendingDeletes(false, &fc_abortrels);
	fc_hdr.ncommitstats =
		pgstat_get_transactional_drops(true, &fc_commitstats);
	fc_hdr.nabortstats =
		pgstat_get_transactional_drops(false, &fc_abortstats);
	fc_hdr.ninvalmsgs = xactGetCommittedInvalidationMessages(&fc_invalmsgs,
														  &fc_hdr.initfileinval);
	fc_hdr.gidlen = strlen(fc_gxact->gid) + 1;	/* 包含 '\0' */
	/* EndPrepare 将填充原始数据（如果需要） */
	fc_hdr.origin_lsn = InvalidXLogRecPtr;
	fc_hdr.origin_timestamp = 0;

	fc_save_state_data(&fc_hdr, sizeof(TwoPhaseFileHeader));
	fc_save_state_data(fc_gxact->gid, fc_hdr.gidlen);

	/*
	 * 添加有关子事务、可删除文件和缓存失效消息的附加信息。
	 */
	if (fc_hdr.nsubxacts > 0)
	{
		fc_save_state_data(fc_children, fc_hdr.nsubxacts * sizeof(TransactionId));
		/* 当我们拥有子事务数据时，将其放入 gxact 中 */
		fc_GXactLoadSubxactData(fc_gxact, fc_hdr.nsubxacts, fc_children);
	}
	if (fc_hdr.ncommitrels > 0)
	{
		fc_save_state_data(fc_commitrels, fc_hdr.ncommitrels * sizeof(RelFileNode));
		pfree(fc_commitrels);
	}
	if (fc_hdr.nabortrels > 0)
	{
		fc_save_state_data(fc_abortrels, fc_hdr.nabortrels * sizeof(RelFileNode));
		pfree(fc_abortrels);
	}
	if (fc_hdr.ncommitstats > 0)
	{
		fc_save_state_data(fc_commitstats,
						fc_hdr.ncommitstats * sizeof(xl_xact_stats_item));
		pfree(fc_commitstats);
	}
	if (fc_hdr.nabortstats > 0)
	{
		fc_save_state_data(fc_abortstats,
						fc_hdr.nabortstats * sizeof(xl_xact_stats_item));
		pfree(fc_abortstats);
	}
	if (fc_hdr.ninvalmsgs > 0)
	{
		fc_save_state_data(fc_invalmsgs,
						fc_hdr.ninvalmsgs * sizeof(SharedInvalidationMessage));
		pfree(fc_invalmsgs);
	}
}

/*
 * 完成准备状态数据并将其写入 WAL。
 */
void EndPrepare(GlobalTransaction fc_gxact)
{
	TwoPhaseFileHeader *fc_hdr;
	StateFileChunk *fc_record;
	bool		fc_replorigin;

	/* 将结束哨兵添加到 2PC 记录列表中 */
	RegisterTwoPhaseRecord(TWOPHASE_RM_END_ID, 0,
						   NULL, 0);

	/* 返回并在文件头记录中填写 total_len */
	fc_hdr = (TwoPhaseFileHeader *) records.head->data;
	Assert(fc_hdr->magic == TWOPHASE_MAGIC);
	fc_hdr->total_len = records.total_len + sizeof(pg_crc32c);

	fc_replorigin = (replorigin_session_origin != InvalidRepOriginId &&
				  replorigin_session_origin != DoNotReplicateId);

	if (fc_replorigin)
	{
		fc_hdr->origin_lsn = replorigin_session_origin_lsn;
		fc_hdr->origin_timestamp = replorigin_session_origin_timestamp;
	}

	/*
	 * 如果数据大小超过 MaxAllocSize，我们将在 ReadTwoPhaseFile 中无法读取它。
	 * 现在检查这一点，而不是在将数据写入文件然后在提交时重新读取时失败。
	 */
	if (fc_hdr->total_len > MaxAllocSize)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("two-phase state file maximum length exceeded")));

	/*
	 * 现在将 2PC 状态数据写入 WAL。我们让 WAL 的 CRC 保护
	 * 保护我们，因此无需单独计算 CRC。
	 *
	 * 我们必须在这里设置 DELAY_CHKPT_START；否则，在 WAL 记录插入后立即开始的
	 * 检查点可能会在不 fsync 我们的状态文件的情况下完成。（这基本上是与
	 * RecordTransactionCommit 使用 DELAY_CHKPT_START 的 COMMIT-to-clog-write
	 * 情况相同的竞争条件；参见那里注释。）
	 *
	 * 我们将 PREPARE 记录的位置保存在 gxact 中，以供 CheckPointTwoPhase 后续使用。
	 */
	XLogEnsureRecordSpace(0, records.num_chunks);

	START_CRIT_SECTION();

	Assert((MyProc->delayChkptFlags & DELAY_CHKPT_START) == 0);
	MyProc->delayChkptFlags |= DELAY_CHKPT_START;

	XLogBeginInsert();
	for (fc_record = records.head; fc_record != NULL; fc_record = fc_record->next)
		XLogRegisterData(fc_record->data, fc_record->len);

	XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);

	fc_gxact->prepare_end_lsn = XLogInsert(RM_XACT_ID, XLOG_XACT_PREPARE);

	if (fc_replorigin)
	{
		/* 向前移动这个复制源的 LSN */
		replorigin_session_advance(replorigin_session_origin_lsn,
								   fc_gxact->prepare_end_lsn);
	}

	XLogFlush(fc_gxact->prepare_end_lsn);

	/* 如果我们现在崩溃，我们已经准备好了：WAL 重播将修复问题 */

	/* 存储记录的起始位置，以便在提交时读取 */
	fc_gxact->prepare_start_lsn = ProcLastRecPtr;

	/*
	 * 将准备好的事务标记为有效。 一旦 xact.c 将 MyProc 标记为
	 * 不在运行我们的 XID（它会在此函数返回后立即执行），其他人就可以提交/回滚该事务。
	 *
	 * 注意：这会导致为准备的 XID 在 ProcArray 中创建一个虚拟条目。
	 * 这必须在我们从 MyProc / ProcGlobal->xids[] 中清除 XID 之前发生，否则
	 * 会有一个窗口，在这个窗口中 XID 不在运行，
	 * 根据 TransactionIdIsInProgress，其他观察者会认为该事务崩溃了。
	 * 相反，我们在 ProcArray 中有一个窗口，其中同一 XID 出现两次，这是可以的。
	 */
	fc_MarkAsPrepared(fc_gxact, false);

	/*
	 * 现在我们可以将自己标记为不在提交关键部分内：
	 * 在此之后开始的检查点肯定会将 gxact 视为 fsync 的候选者。
	 */
	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;

	/*
	 * 请记住，我们锁定了这个 GlobalTransaction 条目。如果
	 * 我们在此之后崩溃，撤销已为时已晚，但我们必须解锁
	 * 以便准备好的事务可以提交或回滚。
	 */
	MyLockedGxact = fc_gxact;

	END_CRIT_SECTION();

	/*
	 * 如果需要，等待同步复制。
	 *
	 * 请注意，在此阶段，我们已标记准备，但仍显示为
	 * 在 procarray 中运行（两次！）并继续持有锁。
	 */
	SyncRepWaitForLSN(fc_gxact->prepare_end_lsn, false);

	records.tail = records.head = NULL;
	records.num_chunks = 0;
}

/*
 * 注册一个 2PC 记录以写入状态文件。
 */
void RegisterTwoPhaseRecord(TwoPhaseRmgrId fc_rmid, uint16 fc_info,
					   const void *fc_data, uint32 fc_len)
{
	TwoPhaseRecordOnDisk fc_record;

	fc_record.rmid = fc_rmid;
	fc_record.info = fc_info;
	fc_record.len = fc_len;
	fc_save_state_data(&fc_record, sizeof(TwoPhaseRecordOnDisk));
	if (fc_len > 0)
		fc_save_state_data(fc_data, fc_len);
}


/*
 * 读取并验证xid的状态文件。
 *
 * 如果看起来正常（具有有效的魔术数字和CRC），则返回palloc分配的
 * 文件内容，当发现损坏数据时发出错误。如果missing_ok为真，
 * 这表明可以安全忽略缺失的文件，则返回NULL。此状态可以在进行恢复时达到。
 */
static char * fc_ReadTwoPhaseFile(TransactionId fc_xid, bool fc_missing_ok)
{
	char		fc_path[MAXPGPATH];
	char	   *fc_buf;
	TwoPhaseFileHeader *fc_hdr;
	int			fc_fd;
	struct stat fc_stat;
	uint32		fc_crc_offset;
	pg_crc32c	fc_calc_crc,
				fc_file_crc;
	int			fc_r;

	TwoPhaseFilePath(fc_path, fc_xid);

	fc_fd = OpenTransientFile(fc_path, O_RDONLY | PG_BINARY);
	if (fc_fd < 0)
	{
		if (fc_missing_ok && errno == ENOENT)
			return NULL;

		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m", fc_path)));
	}

	/*
	 * 检查文件长度。我们可以很容易确定下限。
	 * 我们设定上限以避免在损坏文件上发生palloc()失败，虽然
	 * 我们无法保证即使在有效文件上也不会出现内存不足错误。
	 */
	if (fstat(fc_fd, &fc_stat))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not stat file \"%s\": %m", fc_path)));

	if (fc_stat.st_size < (MAXALIGN(sizeof(TwoPhaseFileHeader)) +
						MAXALIGN(sizeof(TwoPhaseRecordOnDisk)) +
						sizeof(pg_crc32c)) ||
		fc_stat.st_size > MaxAllocSize)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg_plural("incorrect size of file \"%s\": %lld byte",
							   "incorrect size of file \"%s\": %lld bytes",
							   (long long int) fc_stat.st_size, fc_path,
							   (long long int) fc_stat.st_size)));

	fc_crc_offset = fc_stat.st_size - sizeof(pg_crc32c);
	if (fc_crc_offset != MAXALIGN(fc_crc_offset))
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("incorrect alignment of CRC offset for file \"%s\"",
						fc_path)));

	/*
	 * 好的，读取文件内容。
	 */
	fc_buf = (char *) palloc(fc_stat.st_size);

	pgstat_report_wait_start(WAIT_EVENT_TWOPHASE_FILE_READ);
	fc_r = read(fc_fd, fc_buf, fc_stat.st_size);
	if (fc_r != fc_stat.st_size)
	{
		if (fc_r < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read file \"%s\": %m", fc_path)));
		else
			ereport(ERROR,
					(errmsg("could not read file \"%s\": read %d of %lld",
							fc_path, fc_r, (long long int) fc_stat.st_size)));
	}

	pgstat_report_wait_end();

	if (CloseTransientFile(fc_fd) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", fc_path)));

	fc_hdr = (TwoPhaseFileHeader *) fc_buf;
	if (fc_hdr->magic != TWOPHASE_MAGIC)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("invalid magic number stored in file \"%s\"",
						fc_path)));

	if (fc_hdr->total_len != fc_stat.st_size)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("invalid size stored in file \"%s\"",
						fc_path)));

	INIT_CRC32C(fc_calc_crc);
	COMP_CRC32C(fc_calc_crc, fc_buf, fc_crc_offset);
	FIN_CRC32C(fc_calc_crc);

	fc_file_crc = *((pg_crc32c *) (fc_buf + fc_crc_offset));

	if (!EQ_CRC32C(fc_calc_crc, fc_file_crc))
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("calculated CRC checksum does not match value stored in file \"%s\"",
						fc_path)));

	return fc_buf;
}


/*
 * 从xlog读取2PC数据。在检查点期间，这些数据将移至
 * twophase文件，应使用ReadTwoPhaseFile代替。
 *
 * 清楚地注意到，此函数可以在正常操作期间访问WAL，
 * 类似于WALSender或逻辑解码的方式。
 */
static void fc_XlogReadTwoPhaseData(XLogRecPtr fc_lsn, char **fc_buf, int *fc_len)
{
	XLogRecord *fc_record;
	XLogReaderState *fc_xlogreader;
	char	   *fc_errormsg;

	fc_xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
									XL_ROUTINE(.page_read = &read_local_xlog_page,
											   .segment_open = &wal_segment_open,
											   .segment_close = &wal_segment_close),
									NULL);
	if (!fc_xlogreader)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed while allocating a WAL reading processor.")));

	XLogBeginRead(fc_xlogreader, fc_lsn);
	fc_record = XLogReadRecord(fc_xlogreader, &fc_errormsg);

	if (fc_record == NULL)
	{
		if (fc_errormsg)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read two-phase state from WAL at %X/%X: %s",
							LSN_FORMAT_ARGS(fc_lsn), fc_errormsg)));
		else
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read two-phase state from WAL at %X/%X",
							LSN_FORMAT_ARGS(fc_lsn))));
	}

	if (XLogRecGetRmid(fc_xlogreader) != RM_XACT_ID ||
		(XLogRecGetInfo(fc_xlogreader) & XLOG_XACT_OPMASK) != XLOG_XACT_PREPARE)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("expected two-phase state data is not present in WAL at %X/%X",
						LSN_FORMAT_ARGS(fc_lsn))));

	if (fc_len != NULL)
		*fc_len = XLogRecGetDataLen(fc_xlogreader);

	*fc_buf = palloc(sizeof(char) * XLogRecGetDataLen(fc_xlogreader));
	memcpy(*fc_buf, XLogRecGetData(fc_xlogreader), sizeof(char) * XLogRecGetDataLen(fc_xlogreader));

	XLogReaderFree(fc_xlogreader);
}


/*
 * 确认xid已准备好，在恢复期间
 */
bool StandbyTransactionIdIsPrepared(TransactionId fc_xid)
{
	char	   *fc_buf;
	TwoPhaseFileHeader *fc_hdr;
	bool		fc_result;

	Assert(TransactionIdIsValid(fc_xid));

	if (max_prepared_xacts <= 0)
		return false;			/* 无需操作 */

	/* 读取并验证文件 */
	fc_buf = fc_ReadTwoPhaseFile(fc_xid, true);
	if (fc_buf == NULL)
		return false;

	/* 也检查头部 */
	fc_hdr = (TwoPhaseFileHeader *) fc_buf;
	fc_result = TransactionIdEquals(fc_hdr->xid, fc_xid);
	pfree(fc_buf);

	return fc_result;
}

/*
 * 完成准备好的事务：执行COMMIT PREPARED或ROLLBACK PREPARED
 */
void FinishPreparedTransaction(const char *fc_gid, bool fc_isCommit)
{
	GlobalTransaction fc_gxact;
	PGPROC	   *fc_proc;
	TransactionId fc_xid;
	bool		fc_ondisk;
	char	   *fc_buf;
	char	   *fc_bufptr;
	TwoPhaseFileHeader *fc_hdr;
	TransactionId fc_latestXid;
	TransactionId *fc_children;
	RelFileNode *fc_commitrels;
	RelFileNode *fc_abortrels;
	RelFileNode *fc_delrels;
	int			fc_ndelrels;
	xl_xact_stats_item *fc_commitstats;
	xl_xact_stats_item *fc_abortstats;
	SharedInvalidationMessage *fc_invalmsgs;

	/*
	 * 验证GID，并锁定GXACT以确保两个后端不会
	 * 同时尝试提交相同的GID。
	 */
	fc_gxact = fc_LockGXact(fc_gid, GetUserId());
	fc_proc = &ProcGlobal->allProcs[fc_gxact->pgprocno];
	fc_xid = fc_gxact->xid;

	/*
	 * 读取和验证2PC状态数据。状态数据通常会存储在
	 * WAL文件中，如果LSN在最后检查点记录之后，或者由于某种原因
	 * 它们已经存在很长时间则移动到磁盘。
	 */
	if (fc_gxact->ondisk)
		fc_buf = fc_ReadTwoPhaseFile(fc_xid, false);
	else
		fc_XlogReadTwoPhaseData(fc_gxact->prepare_start_lsn, &fc_buf, NULL);


	/*
	 * 拆解头部区域
	 */
	fc_hdr = (TwoPhaseFileHeader *) fc_buf;
	Assert(TransactionIdEquals(fc_hdr->xid, fc_xid));
	fc_bufptr = fc_buf + MAXALIGN(sizeof(TwoPhaseFileHeader));
	fc_bufptr += MAXALIGN(fc_hdr->gidlen);
	fc_children = (TransactionId *) fc_bufptr;
	fc_bufptr += MAXALIGN(fc_hdr->nsubxacts * sizeof(TransactionId));
	fc_commitrels = (RelFileNode *) fc_bufptr;
	fc_bufptr += MAXALIGN(fc_hdr->ncommitrels * sizeof(RelFileNode));
	fc_abortrels = (RelFileNode *) fc_bufptr;
	fc_bufptr += MAXALIGN(fc_hdr->nabortrels * sizeof(RelFileNode));
	fc_commitstats = (xl_xact_stats_item *) fc_bufptr;
	fc_bufptr += MAXALIGN(fc_hdr->ncommitstats * sizeof(xl_xact_stats_item));
	fc_abortstats = (xl_xact_stats_item *) fc_bufptr;
	fc_bufptr += MAXALIGN(fc_hdr->nabortstats * sizeof(xl_xact_stats_item));
	fc_invalmsgs = (SharedInvalidationMessage *) fc_bufptr;
	fc_bufptr += MAXALIGN(fc_hdr->ninvalmsgs * sizeof(SharedInvalidationMessage));

	/* 在所有子项中计算latestXid */
	fc_latestXid = TransactionIdLatest(fc_xid, fc_hdr->nsubxacts, fc_children);

	/* 在清理时防止取消/死亡中断 */
	HOLD_INTERRUPTS();

	/*
	 * 此处操作的顺序至关重要：为提交或中止创建XLOG条目，
	 * 然后在pg_xact中标记事务已提交或已中止，
	 * 然后从全局ProcArray中移除其PGPROC（这意味着
	 * TransactionIdIsInProgress将停止表明准备好的事务正在
	 * 进行），然后运行提交后或中止后回调。回调将释放事务持有的锁。
	 */
	if (fc_isCommit)
		fc_RecordTransactionCommitPrepared(fc_xid,
										fc_hdr->nsubxacts, fc_children,
										fc_hdr->ncommitrels, fc_commitrels,
										fc_hdr->ncommitstats,
										fc_commitstats,
										fc_hdr->ninvalmsgs, fc_invalmsgs,
										fc_hdr->initfileinval, fc_gid);
	else
		fc_RecordTransactionAbortPrepared(fc_xid,
									   fc_hdr->nsubxacts, fc_children,
									   fc_hdr->nabortrels, fc_abortrels,
									   fc_hdr->nabortstats,
									   fc_abortstats,
									   fc_gid);

	ProcArrayRemove(fc_proc, fc_latestXid);

	/*
	 * 如果在运行回调时失败，标记gxact无效，以便
	 * 没有人会尝试提交/回滚，从而在这一点之后如果失败将被回收。
	 * 它仍由我们的后端锁定，因此它不会立刻消失。
	 *
	 * （我们假设在不获取TwoPhaseStateLock的情况下这样做是安全的。）
	 */
	fc_gxact->valid = false;

	/*
	 * 我们必须删除任何应该被丢弃的文件。为了与常规的xact.c代码路径保持一致，
	 * 必须在释放锁之前执行此操作，因此在运行回调之前执行。
	 *
	 * 注意：此代码知道我们不能删除任何临时关系...
	 */
	if (fc_isCommit)
	{
		fc_delrels = fc_commitrels;
		fc_ndelrels = fc_hdr->ncommitrels;
	}
	else
	{
		fc_delrels = fc_abortrels;
		fc_ndelrels = fc_hdr->nabortrels;
	}

	/* 确保应该被丢弃的文件被丢弃 */
	DropRelationFiles(fc_delrels, fc_ndelrels, false);

	if (fc_isCommit)
		pgstat_execute_transactional_drops(fc_hdr->ncommitstats, fc_commitstats, false);
	else
		pgstat_execute_transactional_drops(fc_hdr->nabortstats, fc_abortstats, false);

	/*
	 * 处理缓存失效消息。
	 *
	 * Relcache 初始化文件失效要求在发送 SI 消息之前和之后都进行处理，只有在提交时。见 AtEOXact_Inval().
	 */
	if (fc_isCommit)
	{
		if (fc_hdr->initfileinval)
			RelationCacheInitFilePreInvalidate();
		SendSharedInvalidMessages(fc_invalmsgs, fc_hdr->ninvalmsgs);
		if (fc_hdr->initfileinval)
			RelationCacheInitFilePostInvalidate();
	}

	/*
	 * 获取两阶段锁。我们想在保持锁定的情况下处理两阶段回调，以避免与尝试使用相同 GID 的其他事务发生潜在冲突，因此共享内存状态被清除后锁定才释放。
	 */
	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);

	/* 现在执行回调 */
	if (fc_isCommit)
		fc_ProcessRecords(fc_bufptr, fc_xid, twophase_postcommit_callbacks);
	else
		fc_ProcessRecords(fc_bufptr, fc_xid, twophase_postabort_callbacks);

	PredicateLockTwoPhaseFinish(fc_xid, fc_isCommit);

	/*
	 * 在保持两阶段锁的情况下读取此值，因为在锁释放后，磁盘上的 2PC 文件会物理删除。
	 */
	fc_ondisk = fc_gxact->ondisk;

	/* 清除共享内存状态 */
	fc_RemoveGXact(fc_gxact);

	/*
	 * 释放锁，因为所有回调都已调用，并且共享内存清理已完成。
	 */
	LWLockRelease(TwoPhaseStateLock);

	/* 将准备好的 xact 计数为已提交或已中止 */
	AtEOXact_PgStat(fc_isCommit, false);

	/*
	 * 现在我们可以清理可能留下的任何文件。
	 */
	if (fc_ondisk)
		fc_RemoveTwoPhaseFile(fc_xid, true);

	MyLockedGxact = NULL;

	RESUME_INTERRUPTS();

	pfree(fc_buf);
}

/*
 * 扫描内存中的 2PC 状态数据，并为每个 2PC 记录调用指示的回调。
 */
static void fc_ProcessRecords(char *fc_bufptr, TransactionId fc_xid,
			   const TwoPhaseCallback fc_callbacks[])
{
	for (;;)
	{
		TwoPhaseRecordOnDisk *fc_record = (TwoPhaseRecordOnDisk *) fc_bufptr;

		Assert(fc_record->rmid <= TWOPHASE_RM_MAX_ID);
		if (fc_record->rmid == TWOPHASE_RM_END_ID)
			break;

		fc_bufptr += MAXALIGN(sizeof(TwoPhaseRecordOnDisk));

		if (fc_callbacks[fc_record->rmid] != NULL)
			fc_callbacks[fc_record->rmid] (fc_xid, fc_record->info,
									 (void *) fc_bufptr, fc_record->len);

		fc_bufptr += MAXALIGN(fc_record->len);
	}
}

/*
 * 删除指定 XID 的 2PC 文件。
 *
 * 如果 giveWarning 为假，则不抱怨文件不存在；这是在 WAL 重放期间的预期情况。
 */
static void fc_RemoveTwoPhaseFile(TransactionId fc_xid, bool fc_giveWarning)
{
	char		fc_path[MAXPGPATH];

	TwoPhaseFilePath(fc_path, fc_xid);
	if (unlink(fc_path))
		if (errno != ENOENT || fc_giveWarning)
			ereport(WARNING,
					(errcode_for_file_access(),
					 errmsg("could not remove file \"%s\": %m", fc_path)));
}

/*
 * 重新创建状态文件。这在 WAL 重放和检查点创建期间使用。
 *
 * 注意：内容和长度不包括 CRC。
 */
static void fc_RecreateTwoPhaseFile(TransactionId fc_xid, void *fc_content, int fc_len)
{
	char		fc_path[MAXPGPATH];
	pg_crc32c	fc_statefile_crc;
	int			fc_fd;

	/* 重新计算 CRC */
	INIT_CRC32C(fc_statefile_crc);
	COMP_CRC32C(fc_statefile_crc, fc_content, fc_len);
	FIN_CRC32C(fc_statefile_crc);

	TwoPhaseFilePath(fc_path, fc_xid);

	fc_fd = OpenTransientFile(fc_path,
						   O_CREAT | O_TRUNC | O_WRONLY | PG_BINARY);
	if (fc_fd < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not recreate file \"%s\": %m", fc_path)));

	/* 写入内容和 CRC */
	errno = 0;
	pgstat_report_wait_start(WAIT_EVENT_TWOPHASE_FILE_WRITE);
	if (write(fc_fd, fc_content, fc_len) != fc_len)
	{
		/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
		if (errno == 0)
			errno = ENOSPC;
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m", fc_path)));
	}
	if (write(fc_fd, &fc_statefile_crc, sizeof(pg_crc32c)) != sizeof(pg_crc32c))
	{
		/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
		if (errno == 0)
			errno = ENOSPC;
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m", fc_path)));
	}
	pgstat_report_wait_end();

	/*
	 * 我们必须对文件进行 fsync，因为重放结束的检查点不会这样做，尚没有 GXACT 在共享内存中告诉它。
	 */
	pgstat_report_wait_start(WAIT_EVENT_TWOPHASE_FILE_SYNC);
	if (pg_fsync(fc_fd) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not fsync file \"%s\": %m", fc_path)));
	pgstat_report_wait_end();

	if (CloseTransientFile(fc_fd) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", fc_path)));
}

/*
 * CheckPointTwoPhase -- 处理检查点的 2PC 组件。
 *
 * 我们必须对任何有效或在重做期间生成且 PREPARE LSN <= 检查点重做地平线的 GXACT 的状态文件进行 fsync。 （如果 gxact 尚未有效，尚未在重做中生成，或有更晚的 LSN，则该检查点不负责对其进行 fsync。）
 *
 * 这故意在检查点序列中尽可能晚地运行，因为 GXACT 通常生命周期很短，因此如果我们稍等一下，原本在检查点开始时有效的 GXACT 可能将不再存在。使用典型的检查点设置，对于在线检查点，这大约是 3 分钟，因此我们预计将没有 GXACT 需要复制到磁盘。
 *
 * 如果 GXACT 在多个检查点之间保持有效，它将已经在磁盘上，因此我们不会重复写入。
 */
void CheckPointTwoPhase(XLogRecPtr fc_redo_horizon)
{
	int			fc_i;
	int			fc_serialized_xacts = 0;

	if (max_prepared_xacts <= 0)
		return;					/* 无需操作 */

	TRACE_POSTGRESQL_TWOPHASE_CHECKPOINT_START();

	/*
	 * 我们预计需要复制到磁盘的 GXACTs 数量为零，因此我们在持有 TwoPhaseStateLock 时执行所有 I/O，以简化操作。这阻止了任何新的 xacts 在此过程中进行准备，这应该没有问题，因为长期存在的准备 xacts 表明事务管理器不活跃。
	 *
	 * 也可以将 I/O 移出锁，但在每次错误时，我们应该检查是否有人在不同的后端提交了我们的事务。如果有人发现这个地方造成瓶颈，我们可以将这个优化留到未来。
	 *
	 * 请注意，不可能存在一个 GXACT，其 prepare_end_lsn 在最后一个检查点之前设置却被标记为无效，这是因为延迟检查点标志的努力。
	 */
	LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		/*
		 * 请注意，我们使用 gxact 而不是 PGPROC，因此这也适用于恢复
		 */
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];

		if ((fc_gxact->valid || fc_gxact->inredo) &&
			!fc_gxact->ondisk &&
			fc_gxact->prepare_end_lsn <= fc_redo_horizon)
		{
			char	   *fc_buf;
			int			fc_len;

			fc_XlogReadTwoPhaseData(fc_gxact->prepare_start_lsn, &fc_buf, &fc_len);
			fc_RecreateTwoPhaseFile(fc_gxact->xid, fc_buf, fc_len);
			fc_gxact->ondisk = true;
			fc_gxact->prepare_start_lsn = InvalidXLogRecPtr;
			fc_gxact->prepare_end_lsn = InvalidXLogRecPtr;
			pfree(fc_buf);
			fc_serialized_xacts++;
		}
	}
	LWLockRelease(TwoPhaseStateLock);

	/*
	 * 无条件刷新父目录以确保任何信息在磁盘上持久化。 Two-phase 文件可能已被删除，这些删除需要被持久化，以及自上次检查点以来新创建的任何文件。
	 */
	fsync_fname(TWOPHASE_DIR, true);

	TRACE_POSTGRESQL_TWOPHASE_CHECKPOINT_DONE();

	if (log_checkpoints && fc_serialized_xacts > 0)
		ereport(LOG,
				(errmsg_plural("%u two-phase state file was written "
							   "for a long-running prepared transaction",
							   "%u two-phase state files were written "
							   "for long-running prepared transactions",
							   fc_serialized_xacts,
							   fc_serialized_xacts)));
}

/*
 * restoreTwoPhaseData
 *
 * 扫描 pg_twophase 并根据磁盘数据填充 TwoPhaseState。这在恢复开始时调用一次，以节省将来的额外查找。两阶段文件如果比最小 XID 地平线更新，则会在此过程中被丢弃。
 */
void restoreTwoPhaseData(void)
{
	DIR		   *fc_cldir;
	struct dirent *fc_clde;

	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	fc_cldir = AllocateDir(TWOPHASE_DIR);
	while ((fc_clde = ReadDir(fc_cldir, TWOPHASE_DIR)) != NULL)
	{
		if (strlen(fc_clde->d_name) == 8 &&
			strspn(fc_clde->d_name, "0123456789ABCDEF") == 8)
		{
			TransactionId fc_xid;
			char	   *fc_buf;

			fc_xid = (TransactionId) strtoul(fc_clde->d_name, NULL, 16);

			fc_buf = fc_ProcessTwoPhaseBuffer(fc_xid, InvalidXLogRecPtr,
										true, false, false);
			if (fc_buf == NULL)
				continue;

			PrepareRedoAdd(fc_buf, InvalidXLogRecPtr,
						   InvalidXLogRecPtr, InvalidRepOriginId);
		}
	}
	LWLockRelease(TwoPhaseStateLock);
	FreeDir(fc_cldir);
}

/*
 * PrescanPreparedTransactions
 *
 * 扫描 TwoPhaseState 的共享内存条目，并确定存在的有效 XID 范围。这在数据库启动期间运行，在我们完成读取 WAL 后。 ShmemVariableCache->nextXid 已被设置为高于 WAL 中存在证据的最高 XID。
 *
 * 我们丢弃任何主 XID 超过 nextXid 的准备 xacts —— 如果存在，这表明 DBA 已经进行了 PITR 恢复到早期时间点，而没有清理 pg_twophase。 我们不敢尝试恢复这样的准备 xacts，因为它们可能依赖于现在不存在的数据库状态。
 *
 * 但是，我们会将 nextXid 递增到属于有效准备 xacts 的任何 subxact XID 之上。我们需要这样做，因为 subxact 提交不会写入 WAL 记录，因此这些 subxact XID 可能在 WAL 中没有证据。
 *
 * 对于损坏的两阶段文件，立即失败。保留损坏的条目并让重放继续会对系统造成损害，应该引入新的备份。
 *
 * 我们的另一个责任是确定并返回已准备 xacts 中最旧的有效 XID（如果没有，则返回 ShmemVariableCache->nextXid）。这对于正确地同步 pg_subtrans 启动是必要的。
 *
 * 如果 xids_p 和 nxids_p 不是 NULL，则将所有顶层 xids 的 palloc'd 数组的指针存储在 *xids_p 中。数组中的条目数量将返回在 *nxids_p 中。
 */
TransactionId PrescanPreparedTransactions(TransactionId **fc_xids_p, int *fc_nxids_p)
{
	FullTransactionId fc_nextXid = ShmemVariableCache->nextXid;
	TransactionId fc_origNextXid = XidFromFullTransactionId(fc_nextXid);
	TransactionId fc_result = fc_origNextXid;
	TransactionId *fc_xids = NULL;
	int			fc_nxids = 0;
	int			fc_allocsize = 0;
	int			fc_i;

	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		TransactionId fc_xid;
		char	   *fc_buf;
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];

		Assert(fc_gxact->inredo);

		fc_xid = fc_gxact->xid;

		fc_buf = fc_ProcessTwoPhaseBuffer(fc_xid,
									fc_gxact->prepare_start_lsn,
									fc_gxact->ondisk, false, true);

		if (fc_buf == NULL)
			continue;

		/*
		 * 好的，我们认为这个文件是有效的。将xid纳入运行最小结果中。
		 */
		if (TransactionIdPrecedes(fc_xid, fc_result))
			fc_result = fc_xid;

		if (fc_xids_p)
		{
			if (fc_nxids == fc_allocsize)
			{
				if (fc_nxids == 0)
				{
					fc_allocsize = 10;
					fc_xids = palloc(fc_allocsize * sizeof(TransactionId));
				}
				else
				{
					fc_allocsize = fc_allocsize * 2;
					fc_xids = repalloc(fc_xids, fc_allocsize * sizeof(TransactionId));
				}
			}
			fc_xids[fc_nxids++] = fc_xid;
		}

		pfree(fc_buf);
	}
	LWLockRelease(TwoPhaseStateLock);

	if (fc_xids_p)
	{
		*fc_xids_p = fc_xids;
		*fc_nxids_p = fc_nxids;
	}

	return fc_result;
}

/*
 * StandbyRecoverPreparedTransactions
 *
 * 扫描TwoPhaseState的共享内存条目，并设置所有所需信息，以
 * 允许备用查询将准备好的事务视为仍然处于活动状态。
 *
 * 这个函数在恢复结束时从不被调用 - 此时我们使用
 * RecoverPreparedTransactions()。
 *
 * 这会更新pg_subtrans，以便在恢复期间拍摄的快照中
 * 任何子事务都能正确地被视为进行中。
 */
void StandbyRecoverPreparedTransactions(void)
{
	int			fc_i;

	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		TransactionId fc_xid;
		char	   *fc_buf;
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];

		Assert(fc_gxact->inredo);

		fc_xid = fc_gxact->xid;

		fc_buf = fc_ProcessTwoPhaseBuffer(fc_xid,
									fc_gxact->prepare_start_lsn,
									fc_gxact->ondisk, true, false);
		if (fc_buf != NULL)
			pfree(fc_buf);
	}
	LWLockRelease(TwoPhaseStateLock);
}

/*
 * RecoverPreparedTransactions
 *
 * 扫描TwoPhaseState的共享内存条目并重新加载每个准备好事务的状态
 * （重新获取锁等）。
 *
 * 这在恢复结束时运行，但在我们允许后端写入
 * WAL之前。
 *
 * 在恢复结束时，我们拍摄快照的方式将会改变。我们现在需要
 * 用它们完整的SubTransSetParent()信息标记所有运行中的事务
 * 以允许正常快照在快照溢出时正常工作。
 * 我们在这里做这个是因为根据定义，准备好的事务是唯一
 * 仍在运行的写事务，因此这是必要和完整的。
 */
void RecoverPreparedTransactions(void)
{
	int			fc_i;

	LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		TransactionId fc_xid;
		char	   *fc_buf;
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];
		char	   *fc_bufptr;
		TwoPhaseFileHeader *fc_hdr;
		TransactionId *fc_subxids;
		const char *fc_gid;

		fc_xid = fc_gxact->xid;

		/*
		 * 为事务重建subtrans状态 --- 需要因为
		 * pg_subtrans在重启时不会被保留。请注意，我们正在
		 * 将所有子事务直接链接到顶级XID；
		 * 原本可能有一个更复杂的层次结构，但
		 * 恢复到那个状态并不是必需的。如果准备好的事务
		 * 生成了xid分配记录，可能之前已经设置了
		 * SubTransSetParent。
		 */
		fc_buf = fc_ProcessTwoPhaseBuffer(fc_xid,
									fc_gxact->prepare_start_lsn,
									fc_gxact->ondisk, true, false);
		if (fc_buf == NULL)
			continue;

		ereport(LOG,
				(errmsg("recovering prepared transaction %u from shared memory", fc_xid)));

		fc_hdr = (TwoPhaseFileHeader *) fc_buf;
		Assert(TransactionIdEquals(fc_hdr->xid, fc_xid));
		fc_bufptr = fc_buf + MAXALIGN(sizeof(TwoPhaseFileHeader));
		fc_gid = (const char *) fc_bufptr;
		fc_bufptr += MAXALIGN(fc_hdr->gidlen);
		fc_subxids = (TransactionId *) fc_bufptr;
		fc_bufptr += MAXALIGN(fc_hdr->nsubxacts * sizeof(TransactionId));
		fc_bufptr += MAXALIGN(fc_hdr->ncommitrels * sizeof(RelFileNode));
		fc_bufptr += MAXALIGN(fc_hdr->nabortrels * sizeof(RelFileNode));
		fc_bufptr += MAXALIGN(fc_hdr->ncommitstats * sizeof(xl_xact_stats_item));
		fc_bufptr += MAXALIGN(fc_hdr->nabortstats * sizeof(xl_xact_stats_item));
		fc_bufptr += MAXALIGN(fc_hdr->ninvalmsgs * sizeof(SharedInvalidationMessage));

		/*
		 * 重新创建它的GXACT和虚拟PGPROC。但是，检查它是否
		 * 已在重做中添加并且已拥有共享内存条目。
		 */
		fc_MarkAsPreparingGuts(fc_gxact, fc_xid, fc_gid,
							fc_hdr->prepared_at,
							fc_hdr->owner, fc_hdr->database);

		/* 已恢复，因此重置由重做生成的条目的标志 */
		fc_gxact->inredo = false;

		fc_GXactLoadSubxactData(fc_gxact, fc_hdr->nsubxacts, fc_subxids);
		fc_MarkAsPrepared(fc_gxact, true);

		LWLockRelease(TwoPhaseStateLock);

		/*
		 * 使用资源管理器恢复其他状态（特别是锁）。
		 */
		fc_ProcessRecords(fc_bufptr, fc_xid, twophase_recover_callbacks);

		/*
		 * 在处理完每个准备好的事务后释放备用进程持有的锁。
		 * 因此，我们在任何一个时间点都不需要太多额外的锁。
		 */
		if (InHotStandby)
			StandbyReleaseLockTree(fc_xid, fc_hdr->nsubxacts, fc_subxids);

		/*
		 * 我们完成了此事务的恢复。清除MyLockedGxact，
		 * 就像我们在正常操作期间的PrepareTransaction()中做的那样。
		 */
		PostPrepare_Twophase();

		pfree(fc_buf);

		LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
	}

	LWLockRelease(TwoPhaseStateLock);
}

/*
 * ProcessTwoPhaseBuffer
 *
 * 给定一个事务ID，从磁盘读取它或通过提供的"prepare_start_lsn"
 * 直接通过共享内存xlog记录指针读取它。
 *
 * 如果setParent为真，则设置子事务父链接。
 *
 * 如果setNextXid为真，将ShmemVariableCache->nextXid设置为扫描到的最新值。
 */
static char * fc_ProcessTwoPhaseBuffer(TransactionId fc_xid,
					  XLogRecPtr fc_prepare_start_lsn,
					  bool fc_fromdisk,
					  bool fc_setParent, bool fc_setNextXid)
{
	FullTransactionId fc_nextXid = ShmemVariableCache->nextXid;
	TransactionId fc_origNextXid = XidFromFullTransactionId(fc_nextXid);
	TransactionId *fc_subxids;
	char	   *fc_buf;
	TwoPhaseFileHeader *fc_hdr;
	int			fc_i;

	Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));

	if (!fc_fromdisk)
		Assert(fc_prepare_start_lsn != InvalidXLogRecPtr);

	/* 已处理？ */
	if (TransactionIdDidCommit(fc_xid) || TransactionIdDidAbort(fc_xid))
	{
		if (fc_fromdisk)
		{
			ereport(WARNING,
					(errmsg("removing stale two-phase state file for transaction %u",
							fc_xid)));
			fc_RemoveTwoPhaseFile(fc_xid, true);
		}
		else
		{
			ereport(WARNING,
					(errmsg("removing stale two-phase state from memory for transaction %u",
							fc_xid)));
			PrepareRedoRemove(fc_xid, true);
		}
		return NULL;
	}

	/* 如果XID过新则拒绝 */
	if (TransactionIdFollowsOrEquals(fc_xid, fc_origNextXid))
	{
		if (fc_fromdisk)
		{
			ereport(WARNING,
					(errmsg("removing future two-phase state file for transaction %u",
							fc_xid)));
			fc_RemoveTwoPhaseFile(fc_xid, true);
		}
		else
		{
			ereport(WARNING,
					(errmsg("removing future two-phase state from memory for transaction %u",
							fc_xid)));
			PrepareRedoRemove(fc_xid, true);
		}
		return NULL;
	}

	if (fc_fromdisk)
	{
		/* 读取并验证文件 */
		fc_buf = fc_ReadTwoPhaseFile(fc_xid, false);
	}
	else
	{
		/* 读取xlog数据 */
		fc_XlogReadTwoPhaseData(fc_prepare_start_lsn, &fc_buf, NULL);
	}

	/* 解构头部 */
	fc_hdr = (TwoPhaseFileHeader *) fc_buf;
	if (!TransactionIdEquals(fc_hdr->xid, fc_xid))
	{
		if (fc_fromdisk)
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("corrupted two-phase state file for transaction %u",
							fc_xid)));
		else
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("corrupted two-phase state in memory for transaction %u",
							fc_xid)));
	}

	/*
	 * 检查子事务XID...它们应该都跟随主XID，并且
	 * 他们可能会强制我们提前下一个XID。
	 */
	fc_subxids = (TransactionId *) (fc_buf +
								 MAXALIGN(sizeof(TwoPhaseFileHeader)) +
								 MAXALIGN(fc_hdr->gidlen));
	for (fc_i = 0; fc_i < fc_hdr->nsubxacts; fc_i++)
	{
		TransactionId fc_subxid = fc_subxids[fc_i];

		Assert(TransactionIdFollows(fc_subxid, fc_xid));

		/* 如果需要，更新nextXid */
		if (fc_setNextXid)
			AdvanceNextFullTransactionIdPastXid(fc_subxid);

		if (fc_setParent)
			SubTransSetParent(fc_subxid, fc_xid);
	}

	return fc_buf;
}


/* 
 *	RecordTransactionCommitPrepared
 *
 * 这基本上与 RecordTransactionCommit 相同（如果您更改
 * 此函数请参阅）：特别是，我们必须设置 DELAY_CHKPT_START 以避免
 * 竞争条件。
 *
 * 我们知道该事务至少产生了一个 XLOG 条目（其 PREPARE），
 * 因此无法从中优化掉提交记录。
 */
static void fc_RecordTransactionCommitPrepared(TransactionId fc_xid,
								int fc_nchildren,
								TransactionId *fc_children,
								int fc_nrels,
								RelFileNode *fc_rels,
								int fc_nstats,
								xl_xact_stats_item *fc_stats,
								int fc_ninvalmsgs,
								SharedInvalidationMessage *fc_invalmsgs,
								bool fc_initfileinval,
								const char *fc_gid)
{
	XLogRecPtr	fc_recptr;
	TimestampTz fc_committs = GetCurrentTimestamp();
	bool		fc_replorigin;

	/*
	 * 我们是否在使用复制来源功能？换句话说，
	 * 我们是否在重放远程操作？
	 */
	fc_replorigin = (replorigin_session_origin != InvalidRepOriginId &&
				  replorigin_session_origin != DoNotReplicateId);

	START_CRIT_SECTION();

	/* 请参阅 RecordTransactionCommit 中的注释 */
	Assert((MyProc->delayChkptFlags & DELAY_CHKPT_START) == 0);
	MyProc->delayChkptFlags |= DELAY_CHKPT_START;

	/*
	 * 发出 XLOG 提交记录。请注意，我们将 2PC 提交标记为
	 * 可能具有 AccessExclusiveLocks，因为我们不知道它们是否
	 * 具有。
	 */
	fc_recptr = XactLogCommitRecord(fc_committs,
								 fc_nchildren, fc_children, fc_nrels, fc_rels,
								 fc_nstats, fc_stats,
								 fc_ninvalmsgs, fc_invalmsgs,
								 fc_initfileinval,
								 MyXactFlags | XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK,
								 fc_xid, fc_gid);


	if (fc_replorigin)
		/* 向前移动这个复制源的 LSN */
		replorigin_session_advance(replorigin_session_origin_lsn,
								   XactLastRecEnd);

	/*
	 * 记录提交的时间戳。如果未启用 replorigin，则值来自普通
	 * 提交时间戳；如果 replorigin 已经为我们设置了值，
	 * 则来自 replorigin_session_origin_timestamp。
	 *
	 * 我们在这里不需要再写入 WAL，因为上面写入的提交记录
	 * 已经包含了数据。
	 */
	if (!fc_replorigin || replorigin_session_origin_timestamp == 0)
		replorigin_session_origin_timestamp = fc_committs;

	TransactionTreeSetCommitTsData(fc_xid, fc_nchildren, fc_children,
								   replorigin_session_origin_timestamp,
								   replorigin_session_origin);

	/*
	 * 我们目前在这里不尝试在刷写之前休眠……也没有
	 * 对准备好的事务进行异步提交的任何支持（这个想法
	 * 可能是自相矛盾的）
	 */

	/* 刷新 XLOG 到磁盘 */
	XLogFlush(fc_recptr);

	/* 在 pg_xact 中标记事务已提交 */
	TransactionIdCommitTree(fc_xid, fc_nchildren, fc_children);

	/* 检查点现在可以进行 */
	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;

	END_CRIT_SECTION();

	/*
	 * 根据需要等待同步复制。
	 *
	 * 请注意，在此阶段我们已经标记了 clog，但仍显示为
	 * 在 procarray 中运行，并继续保持锁定。
	 */
	SyncRepWaitForLSN(fc_recptr, true);
}

/*
 *	RecordTransactionAbortPrepared
 *
 * 这基本上与 RecordTransactionAbort 相同。
 *
 * 我们知道该事务至少产生了一个 XLOG 条目（其 PREPARE），
 * 因此无法从中优化掉中止记录。
 */
static void fc_RecordTransactionAbortPrepared(TransactionId fc_xid,
							   int fc_nchildren,
							   TransactionId *fc_children,
							   int fc_nrels,
							   RelFileNode *fc_rels,
							   int fc_nstats,
							   xl_xact_stats_item *fc_stats,
							   const char *fc_gid)
{
	XLogRecPtr	fc_recptr;
	bool		fc_replorigin;

	/*
	 * 我们是否在使用复制来源功能？换句话说，
	 * 我们是否在重放远程操作？
	 */
	fc_replorigin = (replorigin_session_origin != InvalidRepOriginId &&
				  replorigin_session_origin != DoNotReplicateId);

	/*
	 * 捕获在 RecordTransactionCommitPrepared 过程中
	 * 部分中止的场景...
	 */
	if (TransactionIdDidCommit(fc_xid))
		elog(PANIC, "cannot abort transaction %u, it was already committed",
			 fc_xid);

	START_CRIT_SECTION();

	/*
	 * 发出 XLOG 提交记录。请注意，我们将 2PC 中止标记为
	 * 可能具有 AccessExclusiveLocks，因为我们不知道它们是否
	 * 具有。
	 */
	fc_recptr = XactLogAbortRecord(GetCurrentTimestamp(),
								fc_nchildren, fc_children,
								fc_nrels, fc_rels,
								fc_nstats, fc_stats,
								MyXactFlags | XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK,
								fc_xid, fc_gid);

	if (fc_replorigin)
		/* 向前移动这个复制源的 LSN */
		replorigin_session_advance(replorigin_session_origin_lsn,
								   XactLastRecEnd);

	/* 始终刷写，因为我们即将删除 2PC 状态文件 */
	XLogFlush(fc_recptr);

	/*
	 * 在 clog 中标记事务已中止。这并不是绝对必要的，
	 * 但我们可以在这里顺便做一下。
	 */
	TransactionIdAbortTree(fc_xid, fc_nchildren, fc_children);

	END_CRIT_SECTION();

	/*
	 * 根据需要等待同步复制。
	 *
	 * 请注意，在此阶段我们已经标记了 clog，但仍显示为
	 * 在 procarray 中运行，并继续保持锁定。
	 */
	SyncRepWaitForLSN(fc_recptr, false);
}

/*
 * PrepareRedoAdd
 *
 * 在共享内存的 TwoPhaseState 结构中的 gxact 条目中存储
 * 指向 WAL 记录的开始/结束及 xid 的指针。如果调用者
 * 指定 InvalidXLogRecPtr 作为获取两阶段数据的 WAL
 * 位置，则该条目被标记为位于磁盘上。
 */
void PrepareRedoAdd(char *fc_buf, XLogRecPtr fc_start_lsn,
			   XLogRecPtr fc_end_lsn, RepOriginId fc_origin_id)
{
	TwoPhaseFileHeader *fc_hdr = (TwoPhaseFileHeader *) fc_buf;
	char	   *fc_bufptr;
	const char *fc_gid;
	GlobalTransaction fc_gxact;

	Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
	Assert(RecoveryInProgress());

	fc_bufptr = fc_buf + MAXALIGN(sizeof(TwoPhaseFileHeader));
	fc_gid = (const char *) fc_bufptr;

	/*
	 * 为给定事务在重做代码路径中保留 GID。
	 *
	 * 这会创建一个 gxact 结构并将其放入活动数组中。
	 *
	 * 在重做过程中，该结构主要用于跟踪共享内存中的 PREPARE/COMMIT 记录。
	 * 因此，我们在这里只填写最低限度的内容。
	 * gxact 还被标记为 gxact->inredo 设置为 true，以指示
	 * 它在重做阶段被添加。
	 */

	/*
	 * 在检查点运行时发生崩溃的情况下，可能会出现一些两阶段数据在其
	 * 相应的记录需要在后续恢复中重播时已被写入磁盘的情况。
	 * 由于 2PC 数据已在磁盘上，它在恢复开始时通过 restoreTwoPhaseData() 已被恢复，
	 * 因此跳过此记录以避免在 TwoPhaseState 中出现重复。
	 * 如果已达到一致状态，则记录将添加到 TwoPhaseState 中，并且在 pg_twophase 中
	 * 不应有相应的文件。
	 */
	if (!XLogRecPtrIsInvalid(fc_start_lsn))
	{
		char		fc_path[MAXPGPATH];

		TwoPhaseFilePath(fc_path, fc_hdr->xid);

		if (access(fc_path, F_OK) == 0)
		{
			ereport(reachedConsistency ? ERROR : WARNING,
					(errmsg("could not recover two-phase state file for transaction %u",
							fc_hdr->xid),
					 errdetail("Two-phase state file has been found in WAL record %X/%X, but this transaction has already been restored from disk.",
							   LSN_FORMAT_ARGS(fc_start_lsn))));
			return;
		}

		if (errno != ENOENT)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not access file \"%s\": %m", fc_path)));
	}

	/* 从空闲列表中获取一个免费 gxact */
	if (TwoPhaseState->freeGXacts == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("maximum number of prepared transactions reached"),
				 errhint("Increase max_prepared_transactions (currently %d).",
						 max_prepared_xacts)));
	fc_gxact = TwoPhaseState->freeGXacts;
	TwoPhaseState->freeGXacts = fc_gxact->next;

	fc_gxact->prepared_at = fc_hdr->prepared_at;
	fc_gxact->prepare_start_lsn = fc_start_lsn;
	fc_gxact->prepare_end_lsn = fc_end_lsn;
	fc_gxact->xid = fc_hdr->xid;
	fc_gxact->owner = fc_hdr->owner;
	fc_gxact->locking_backend = InvalidBackendId;
	fc_gxact->valid = false;
	fc_gxact->ondisk = XLogRecPtrIsInvalid(fc_start_lsn);
	fc_gxact->inredo = true;		/* 是的，在重做中添加 */
	strcpy(fc_gxact->gid, fc_gid);

	/* 并将其插入到活动数组中 */
	Assert(TwoPhaseState->numPrepXacts < max_prepared_xacts);
	TwoPhaseState->prepXacts[TwoPhaseState->numPrepXacts++] = fc_gxact;

	if (fc_origin_id != InvalidRepOriginId)
	{
		/* 恢复应用进度 */
		replorigin_advance(fc_origin_id, fc_hdr->origin_lsn, fc_end_lsn,
						   false /* 向后 */ , false /* WAL */ );
	}

	elog(DEBUG2, "added 2PC data in shared memory for transaction %u", fc_gxact->xid);
}

/*
 * PrepareRedoRemove
 *
 * 从 TwoPhaseState 中移除相应的 gxact 条目。如果一个准备好的事务
 * 通过先前的检查点被保存，还要移除 2PC 文件。
 *
 * 调用者必须在独占模式下持有 TwoPhaseStateLock，因为 TwoPhaseState 
 * 将被更新。
 */
void PrepareRedoRemove(TransactionId fc_xid, bool fc_giveWarning)
{
	GlobalTransaction fc_gxact = NULL;
	int			fc_i;
	bool		fc_found = false;

	Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
	Assert(RecoveryInProgress());

	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		fc_gxact = TwoPhaseState->prepXacts[fc_i];

		if (fc_gxact->xid == fc_xid)
		{
			Assert(fc_gxact->inredo);
			fc_found = true;
			break;
		}
	}

	/*
	 * 如果没有任何内容则直接返回，在 WAL 重放期间这是预期的。
	 */
	if (!fc_found)
		return;

	/*
	 * 现在我们可以清理可能留下的任何文件。
	 */
	elog(DEBUG2, "removing 2PC data for transaction %u", fc_xid);
	if (fc_gxact->ondisk)
		fc_RemoveTwoPhaseFile(fc_xid, fc_giveWarning);
	fc_RemoveGXact(fc_gxact);
}

/*
 * LookupGXact
 *		检查具有给定 GID、lsn 和时间戳的准备事务是否存在。
 *
 * 请注意，我们始终与 prepare 结束时的 LSN 进行比较，因为那是
 * 存储为 origin_lsn 的 2PC 文件中的内容。
 *
 * 此函数主要用于检查来自上游（远程节点）的准备事务是否已存在。
 * 仅检查 GID 不够，因为同一节点上可能存在不同的具有相同 GID 的准备事务。
 * 因此，我们确保匹配准备事务的 origin_lsn 和 origin_timestamp，以避免
 * 来自两个不同节点的准备事务匹配的可能性。
 */
bool LookupGXact(const char *fc_gid, XLogRecPtr fc_prepare_end_lsn,
			TimestampTz fc_origin_prepare_timestamp)
{
	int			fc_i;
	bool		fc_found = false;

	LWLockAcquire(TwoPhaseStateLock, LW_SHARED);
	for (fc_i = 0; fc_i < TwoPhaseState->numPrepXacts; fc_i++)
	{
		GlobalTransaction fc_gxact = TwoPhaseState->prepXacts[fc_i];

		/* 忽略尚未有效的 GID。 */
		if (fc_gxact->valid && strcmp(fc_gxact->gid, fc_gid) == 0)
		{
			char	   *fc_buf;
			TwoPhaseFileHeader *fc_hdr;

			/*
			 * 我们不期望发布者和订阅者之间发生 GXACT（相同 gid）的冲突，
			 * 所以我们在持有 TwoPhaseStateLock 时执行所有 I/O 以简化过程。
			 *
			 * 为了将 I/O 移出锁定，我们需要确保在此期间没有其他后端提交准备事务。
			 * 如果我们在发布者和订阅者之间遇到许多 GID 冲突，我们可以进行此优化。
			 */
			if (fc_gxact->ondisk)
				fc_buf = fc_ReadTwoPhaseFile(fc_gxact->xid, false);
			else
			{
				Assert(fc_gxact->prepare_start_lsn);
				fc_XlogReadTwoPhaseData(fc_gxact->prepare_start_lsn, &fc_buf, NULL);
			}

			fc_hdr = (TwoPhaseFileHeader *) fc_buf;

			if (fc_hdr->origin_lsn == fc_prepare_end_lsn &&
				fc_hdr->origin_timestamp == fc_origin_prepare_timestamp)
			{
				fc_found = true;
				pfree(fc_buf);
				break;
			}

			pfree(fc_buf);
		}
	}
	LWLockRelease(TwoPhaseStateLock);
	return fc_found;
}
